from __future__ import annotations
import os
import time
import uuid
from threading import Thread
from redis import StrictRedis


# 同一时间只能被一个机器的一个线程执行
# 高可用的获取锁与释放锁
# 高性能的获取锁与释放锁
# 具备可重入机制
# 具备锁失效机制，防止死锁
# 具备非阻塞锁特性


# 获取锁时：
# 1.当获取不到锁时，自旋
# 2.当获取到锁时，设置过期时间，防止死锁，当设置上锁时，该计算机宕机了，则其他计算机永远获取不到锁
# 针对这种情况，需要设置过期时间。
# 3.过期时间的设置，需要用set命令，不能使用setnx+expire命令，因为setnx+expire命令是非原子性操作，
# 当执行到setnx上锁后，如果这时宕机，那么便没有设置上过期时间。所以使用set命令。
# 4.获取锁并设置过期时间后，当到达超时时间后，还没有处理完成，这时就会产生错误，需要在数据处理完成后释放锁。
# 这时，如果计算机宕机，也没有问题，因为之前已经设置了超时时间了。
# 释放锁时：
# 1.当释放锁时，如果直接释放，就会出现一种情况，当计算机1在超时后，还没有执行完成，那么这时，计算机2
# 获取到锁，这时计算机1执行完成后，删除锁，删除的是计算机2的锁，就会产生错误。所以需要在删除时判断，是否是自己的锁
# 2.如果直接获取锁，判断相同后删除，这种方法不是原子性操作，也可能会发生异常，用Lua语言，实现原子性操作

redis_config = {
    'host': 'localhost',
    'port': 6379,
    'db': 0
}


class Lock(object):
    SERVER_NAME = "%s:lock"

    def __init__(self, server_name: str) -> None:
        self.redis = self._connection()
        self.server_name = self.SERVER_NAME % server_name

    def acquire(self, exp: int = 10) -> str:
        value = '{}_{}'.format(uuid.getnode(), os.getpid())
        while True:
            if self.redis.set(name=self.server_name, value=value, nx=True, ex=exp):
                # 添加守护线程为超过过期时间的任务，增加过期时间
                return value
            else:
                time.sleep(0.1)

    def release(self, value: str) -> None:
        # 需要判断当前要删除的锁是否是自己的锁，防止误删
        # 通过Lua，让操作变为原子性
        script = """
        if redis.call("get",KEYS[1]) == ARGV[1] then
            return redis.call("del",KEYS[1])
        else
            return 0
        end
        """
        unlock = self.redis.register_script(script)
        unlock(keys=[self.server_name], args=[value])

    def _connection(self) -> StrictRedis:
        return StrictRedis(**redis_config)


